﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using CodeIn.Modules.Graph.Elements;
using CodeIn.Modules.Graph.Modules.Ui.Flow.Engine;
using System.Globalization;
using CodeIn.Ui;
using CodeIn.Core;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows.Controls;
using System.ComponentModel;

namespace CodeIn.Modules.Graph.Modules.Ui.Flow.View {
	public class FlowVisualHost : Canvas {
		private VisualCollection children;

		private IList<LinkVisual> linkVisuals;
		private IDictionary<DataFlowNode, NodeVisual> visualMap;
		private IDictionary<NodeVisual, Sprite> spriteMap;
		private FlowEngine engine;
		private IList<Sprite> draggedSprites;

		public FlowVisualHost() {
			children = new VisualCollection(this);

			engine = new FlowEngine();
			linkVisuals = new List<LinkVisual>();
			visualMap = new Dictionary<DataFlowNode, NodeVisual>();
			spriteMap = new Dictionary<NodeVisual, Sprite>();
			draggedSprites = new List<Sprite>();

			ClipToBounds = true;

			if (!DesignerProperties.GetIsInDesignMode(this)) {
				string dampingStr = OptionManager.Instance.GetValueFromName("Modules.Graph.Ui.Flow.DampingCoeff");
				string repulsionStr = OptionManager.Instance.GetValueFromName("Modules.Graph.Ui.Flow.RepulsionCoeff");
				string attractionStr = OptionManager.Instance.GetValueFromName("Modules.Graph.Ui.Flow.AttractionCoeff");

				engine.Damping = double.Parse(dampingStr, CultureInfo.InvariantCulture);
				engine.Repulsion = double.Parse(repulsionStr, CultureInfo.InvariantCulture);
				engine.Attraction = double.Parse(attractionStr, CultureInfo.InvariantCulture);
			}

			Unloaded += (s, e) => {
				engine.Stop();
			};

			Loaded += (s, e) => {
				engine.Start();
			};

			MouseLeftButtonDown += (s, e) => {
				Point mousePos = e.GetPosition(this);
				HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, mousePos);
				if (hitTestResult != null) {
					NodeVisual nodeVisual = hitTestResult.VisualHit as NodeVisual;
					if (nodeVisual != null) {
						Sprite sp = spriteMap[nodeVisual];
						draggedSprites.Add(sp);
						sp.IsDragged = true;
						sp.P = mousePos;
					}
				}
			};

			MouseLeftButtonUp += (s, e) => {
				foreach (Sprite sp in draggedSprites)
					sp.IsDragged = false;
				draggedSprites.Clear();
			};

			MouseMove += (s, e) => {
				if (e.LeftButton == MouseButtonState.Pressed) {
					Point mousePos = e.GetPosition(this);
					foreach (Sprite sp in draggedSprites)
						sp.P = mousePos;
				}
			};
		}

		// --------------------------------------------------------------------
		// Factory
		// --------------------------------------------------------------------

		private void BuildViewFromDataFlowGraph(DataFlowGraph graph) {
			children.Clear();

			if (graph != null) {
				var nodes = graph.Nodes;
				Random rand = new Random();

				foreach (DataFlowNode node in nodes) {
					double x = rand.NextDouble() * ActualWidth;
					double y = rand.NextDouble() * ActualHeight;
					double radius = FlowResources.NodeRadius;

					NodeVisual nodeVisual = new NodeVisual();
					Sprite sp = engine.AddSprite(radius);
					sp.P = new Point(x, y);

					children.Add(nodeVisual);
					visualMap.Add(node, nodeVisual);
					spriteMap.Add(nodeVisual, sp);
				}

				foreach (DataFlowNode node in nodes) {
					var nodeChildren = graph.GetChildren(node);
					NodeVisual nodeVisual = visualMap[node];
					Sprite nodeSprite = spriteMap[nodeVisual];

					foreach (var child in nodeChildren) {
						NodeVisual childVisual = visualMap[child];
						Sprite childSprite = spriteMap[childVisual];

						LinkVisual linkVisual = new LinkVisual(nodeVisual, childVisual);
						children.Insert(0, linkVisual);
						linkVisuals.Add(linkVisual);
						linkVisual.UpdateVisual();

						nodeSprite.Neighbors.Add(childSprite);
						childSprite.Neighbors.Add(nodeSprite);
					}
				}

				engine.Updated += OnEngineUpdated;
			}
		}

		private void OnEngineUpdated(object sender, EventArgs e) {
			UiHelper.Synchronize(() => {
				foreach (NodeVisual visual in spriteMap.Keys) {
					Sprite sp = spriteMap[visual];
					visual.P = sp.P;
				}

				for (int i = 0; i < linkVisuals.Count; i++) {
					LinkVisual linkVisual = linkVisuals[i];
					linkVisual.UpdateVisual();
				}
			});
		}

		// --------------------------------------------------------------------
		// Visual overrides
		// --------------------------------------------------------------------

		protected override int VisualChildrenCount {
			get { return children.Count; }
		}

		protected override Visual GetVisualChild(int index) {
			if (index < 0 || index >= children.Count)
				throw new ArgumentOutOfRangeException("index");
			return children[index];
		}

		// --------------------------------------------------------------------
		// Dependency properties
		// --------------------------------------------------------------------

		public DataFlowGraph DataFlowGraph {
			get { return (DataFlowGraph)GetValue(DataFlowGraphProperty); }
			set { SetValue(DataFlowGraphProperty, value); }
		}

		public static readonly DependencyProperty DataFlowGraphProperty = DependencyProperty.Register(
			"DataFlowGraph", typeof(DataFlowGraph), typeof(FlowVisualHost),
			new UIPropertyMetadata(null, new PropertyChangedCallback(OnDataFlowGraphPropertyChanged)));

		private static void OnDataFlowGraphPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
			FlowVisualHost host = (FlowVisualHost)sender;
			DataFlowGraph graph = (DataFlowGraph)e.NewValue;

			if (host.IsLoaded)
				host.BuildViewFromDataFlowGraph(graph);
		}
	}
}
